/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦]
 * 
 * https://zhiqim.org/project/zhiqim_framework/zhiqim_httpd.htm
 *
 * Zhiqim Httpd is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.httpd;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import org.zhiqim.kernel.util.Ints;
import org.zhiqim.kernel.util.Zips;


/**
 * HTTP阻塞式输入流，通过ByteArrayOutputStream实现header和content分隔
 *
 * @version v1.0.0 @author zouzhigang 2014-3-21 新建与整理
 */
public class HttpOutputStream extends OutputStream implements HttpdConstants
{
    private final HttpOutputStreamWrap output;
    private int chunkSize = _MAX_CHUNKED_SIZE_;
    
    private HttpSenderImpl sender;
    private ByteArrayOutputStream content;
    
    private boolean chunked;
    private boolean headed;
    
    public HttpOutputStream(HttpConnection conn)
    {
        this.output = new HttpOutputStreamWrap(conn);
        this.content = new ByteArrayOutputStream();
    }
    
    public void setSender(HttpSenderImpl sender)
    {
        this.sender = sender;
    }
    
    public void setChunkSize(int chunkSize)
    {
        if (chunkSize < 4 * KiB)
            this.chunkSize = 4 * KiB;
        else if (chunkSize > _MAX_CHUNKED_SIZE_)
            this.chunkSize = _MAX_CHUNKED_SIZE_;
        else
            this.chunkSize = chunkSize;
    }
    
    /*************************************************************************************/
    //继承和实现的方法
    /*************************************************************************************/
    
    public void write(int b) throws IOException
    {
        if (sender.isCommitted())
            return;
        
        if (content.size() >= chunkSize)
        {//上次最后满一次分块
            flush();
        }
        
        content.write(b);
    }

    public void write(byte[] b, int off, int len) throws IOException
    {
        if (sender.isCommitted())
            return;
        
        if (content.size() >= chunkSize)
        {//上次最后满一次分块
            flush();
        }
        
        if (content.size() + len <= chunkSize)
        {//不足或刚好一次分块，直接添加即可
            content.write(b, off, len);
            return;
        }

        //超过分块数
        int page = (content.size() + len - 1) / chunkSize + 1;
        
        //先写第一块，并刷入
        int firstLen = chunkSize - content.size();
        content.write(b, off, firstLen);
        flush();
        
        //后续块
        int length = firstLen;
        for (int i=1;i<page;i++)
        {
            int count = (i<page-1)?chunkSize:len-length;
            content.write(b, off+length, count);
            length += count;
            
            if (count >= chunkSize)
            {//满即刷入
                flush();
            }
        }
    }
    
    public void flush() throws IOException
    {
        if (!headed)
        {
            output.write(sender.buildChunkedHeader(true));
            headed = true;
        }
        
        writeChunked();
    }
    
    /*************************************************************************************/
    //内部调用的方法
    /*************************************************************************************/
    
    /** 重置内容，如前面有写入缓冲，但未提交发现错误 */
    public void reset()
    {
        content.reset();
    }
    
    /** 提交流 */
    public void commit() throws IOException
    {
        if (!chunked)
        {//未分块，写入头和内容
            output.write(sender.buildChunkedHeader(false));
            content.writeTo(output);
            output.flush();
        }
        else
        {//分块，写入最后块和结尾
            if (content.size() > 0){
                writeChunked();
            }
            
            writeChunkedEnd();
        }
    }
    
    /** 关闭流 */
    public void finished() throws IOException
    {
        if (content != null)
        {
            content.close();
            content = null;
        }
        
        output.close();
    }
    
    /*************************************************************************************/
    //整包写入
    /*************************************************************************************/
    
    public boolean isChunked()
    {
        return chunked;
    }
    
    public boolean processGZipCompress()
    {
        try
        {
            content = Zips.gzip(content);
            return true;
        }
        catch (Exception ex)
        {
            return false;
        }
    }
    
    /** 获取写入的长度 */
    public long getOutputLength()
    {
        return output.length();
    }
    
    public int getContentLength()
    {
        return content.size();
    }
    
    /*************************************************************************************/
    //写入分块数据
    /*************************************************************************************/
    
    private void writeChunked() throws IOException
    {
        output.write(Ints.toBytesHex(content.size()));
        output.write(_CRLF_);
        content.writeTo(output);
        output.write(_CRLF_);
        output.flush();
        
        content.reset();
        chunked = true;
    }
    
    private void writeChunkedEnd() throws IOException
    {
        output.write(_0_);
        output.write(_CRLF_);
        output.write(_CRLF_);
        output.flush();
    }
}
